package com.google.code.sharecontent.action;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URL;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.TagField;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.async.CronSchedule;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Log;

import com.google.code.sharecontent.model.Content;
import com.google.code.sharecontent.model.Resource;
import com.google.code.sharecontent.model.ResourceLog;
import com.google.code.sharecontent.model.Status;
import com.google.code.sharecontent.model.Type;

@Name("scanApplication")
@Scope(ScopeType.APPLICATION)
@Startup
public class ScanApplication {

	@Logger
	Log log;
	@In("daoAction")
	private DaoAction daoAction;

	Status statusAdded = null;
	Status statusWorking = null;
	Status statusActive = null;
	Status statusFounded = null;
	Status statusToProcess = null;
	Status statusWaitingСonfirm = null;
	Status statusNotAccessForProcess = null;

	Type typeMusic = null;

	private final static String WEB_SEPARATOR = "/";

	@Create
	public void start() throws Exception {
		log.info("Start scanApplication");
	}

	boolean resourceScannerWorking = false;
	String baseUrl;

	@Observer("ResourceScanner")
	public void doResourceScanner() {
		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		if (resourceScannerWorking) {
			log.info("ResourceScanner can not start");
			return;
		}
		resourceScannerWorking = true;
		log.info("ResourceScanner start");
		scanResources();
		scanContents();
		resourceScannerWorking = false;
		log.info("ResourceScanner stop");
	}

	private void scanContents() {
		// ищем отмеченые на обработку Элементы
		try {
			List<Content> listContents = this.daoAction.getContentDao().list(" STATUS_ID=" + getStatusToProcess().getId() + "", "ID");
			for (Content content : listContents) {
				try {
					// change status to Working
					this.daoAction.getContentDao().changeStatus(content, getStatusWorking());

					// start parse
					Content contentDownloaded = null;
					contentDownloaded = downloadAndParseMp3(content);

					if (contentDownloaded != null) {
						this.daoAction.getContentDao().changeStatus(content, getStatusWaitingСonfirm());
						this.daoAction.getContentDao().save(contentDownloaded);
					} else {
						this.daoAction.getContentDao().changeStatus(content, getStatusNotAccessForProcess());
					}
				} catch (Exception e) {
					// change status to Error
					if (content != null) {
						// FIXME повесить куда нибуд сообщение
						try {
							this.daoAction.getContentDao().changeStatus(content, getStatusNotAccessForProcess());
//							content = daoAction.getContentDao().get(content.getId());
//							content.setStatusId(getStatusNotAccessForProcess().getId());
//							this.daoAction.getContentDao().save(content);
						} catch (Exception e1) {
							log.error(e1.getMessage());
							;
						}
					}
					System.out.println(e.getMessage());
				}
			}
		} catch (SQLException e2) {
			log.error(e2.getMessage());
		}
	}

	private Content downloadAndParseMp3(Content content) {
		try {
			URL url = new URL(content.getUrl());
			if ("http".equals(url.getProtocol())) {
				content = downloadAndParseMp3Http(content);
			}
			if ("ftp".equals(url.getProtocol())) {
				content = downloadAndParseMp3Ftp(content);
			}
		} catch (HttpException e) {
			log.error(e.getMessage());
			return null;
		} catch (IOException e) {
			log.error(e.getMessage());
			return null;
		}
		// return null;
		return content;
	}

	private Content downloadAndParseMp3Ftp(Content content) throws IOException, HttpException, FileNotFoundException {
//		FTPClient ftp = new FTPClient();
		URL url = new URL(content.getUrl());
		ftp.connect(url.getHost());
		ftp.login("anonymous", "anonymous");
		ftp.setControlEncoding("UTF-8");
		System.out.println("Connected to " + url.getHost() + ".");
		System.out.print(ftp.getReplyString());

		// get file
		String folder = url.getPath();
		ftp.changeWorkingDirectory(folder);
		String tempMp3 = System.getProperty("java.io.tmpdir") + File.separator + content.getFilename();
		File tempMp3File = new File(tempMp3);
		FileOutputStream fos = new FileOutputStream(tempMp3File);
		if (ftp.retrieveFile(url.getFile(), fos)) {
			fos.close();
			log.info("Saved to " + tempMp3);

			content = parseMp3_method2(content, tempMp3File);
		} else {
			fos.close();
			log.info("ERROR Save to " + tempMp3);
			content = null;
		}
		ftp.logout();
		ftp.disconnect();

		boolean del = tempMp3File.delete();
		log.info("File has deleted " + del);

		return content;
	}

	HttpClient client = new HttpClient();
	GetMethod method = new GetMethod();
	private Content downloadAndParseMp3Http(Content content) throws IOException, HttpException, FileNotFoundException {
		method = new GetMethod(content.getUrl());
		log.info("scan content: " + content.getUrl());
		int status = client.executeMethod(method);

		if (status != HttpStatus.SC_OK) {
			log.error("Method failed: " + method.getStatusLine());
			return null;
		}

		String paramDate = null;
		String paramLastModified = null;
		Integer paramContentLength = null;
		Header header;
		if ((header = method.getResponseHeader("Date")) != null) {
			paramDate = header.getValue();
		}
		if ((header = method.getResponseHeader("Last-Modified")) != null) {
			paramLastModified = header.getValue();
		}
		if ((header = method.getResponseHeader("Content-Length")) != null) {
			paramContentLength = new Integer(header.getValue());
		}
		content.setParamDate(paramDate);
		content.setParamLastModifier(paramLastModified);
		content.setSize(paramContentLength);

		String tempMp3 = System.getProperty("java.io.tmpdir") + File.separator + content.getFilename();
		File tempMp3File = new File(tempMp3);
		FileOutputStream fos = new FileOutputStream(tempMp3File);
		InputStream is = method.getResponseBodyAsStream();
		int b;
		while ((b = is.read()) != -1) {
			fos.write(b);
		}
		fos.flush();
		fos.close();
		log.info("Saved to " + tempMp3);

		content = parseMp3_method2(content, tempMp3File);
		boolean del = tempMp3File.delete();
		log.info("File has deleted " + del);
		return content;
	}

	private Content parseMp3_method2(Content info, File file) {
		try {
			// MP3File mp3file = new MP3File(file, MP3File.LOAD_ALL, true);
			MP3File mp3file = new MP3File(file, MP3File.LOAD_ALL, true, "Cp1251");
			boolean hasID3v2Tag = mp3file.hasID3v2Tag();
			boolean hasID3v1Tag = mp3file.hasID3v1Tag();

			
			
			// title
			if (hasID3v2Tag) {
				if (mp3file.getID3v2Tag().getFirstTitle() != null && mp3file.getID3v2Tag().getFirstTitle().length() > 0) {
					info.setTitle(mp3file.getID3v2Tag().getFirstTitle());
				}
				if (mp3file.getID3v2Tag().getArtist() != null && mp3file.getID3v2Tag().getArtist().size() > 0) {
					info.setArtist(fieldsToString(mp3file.getID3v2Tag().getArtist()));
				}
				if (mp3file.getID3v2Tag().getAlbum() != null && mp3file.getID3v2Tag().getAlbum().size() > 0) {
					info.setAlbum(fieldsToString(mp3file.getID3v2Tag().getAlbum()));
				}
				if (mp3file.getID3v2Tag().getGenre() != null && mp3file.getID3v2Tag().getGenre().size() > 0) {
					info.setGenre(fieldsToString(mp3file.getID3v2Tag().getGenre()));
				}
				// if (mp3file.getID3v2Tag().getIdentifier() != null) {
				// info.setIdentifier(mp3file.getID3v2Tag().getIdentifier());
				// }
				if (mp3file.getID3v2Tag().getTrack() != null && mp3file.getID3v2Tag().getTrack().size() > 0) {
					info.setTrack(fieldsToString(mp3file.getID3v2Tag().getTrack()));
				}
				if (mp3file.getID3v2Tag().getYear() != null && mp3file.getID3v2Tag().getYear().size() > 0) {
					info.setYear(fieldsToString(mp3file.getID3v2Tag().getYear()));
				}
			}
			if (hasID3v1Tag) {
				if (mp3file.getID3v1Tag().getFirstTitle() != null && mp3file.getID3v1Tag().getFirstTitle().length() > 0) {
					info.setTitle(mp3file.getID3v1Tag().getFirstTitle());
				}
				// Artist
				if (mp3file.getID3v1Tag().getArtist() != null && mp3file.getID3v1Tag().getArtist().size() > 0) {
					info.setArtist(fieldsToString(mp3file.getID3v1Tag().getArtist()));
				}
				// Album
				if (mp3file.getID3v1Tag().getAlbum() != null && mp3file.getID3v1Tag().getAlbum().size() > 0) {
					info.setAlbum(fieldsToString(mp3file.getID3v1Tag().getAlbum()));
				}
				// Genre
				if (mp3file.getID3v1Tag().getGenre() != null && mp3file.getID3v1Tag().getGenre().size() > 0) {
					info.setGenre(fieldsToString(mp3file.getID3v1Tag().getGenre()));
				}
				// Identifie
				// if (mp3file.getID3v1Tag().getIdentifier() != null) {
				// info.setIdentifier(mp3file.getID3v1Tag().getIdentifier());
				// }
				// Track
				/*
				 * else if (mp3file.hasID3v1Tag() &&
				 * mp3file.getID3v1Tag().getTrack() != null &&
				 * mp3file.getID3v1Tag().getTrack().size()>0) {
				 * info.setTrack(fieldsToString
				 * (mp3file.getID3v1Tag().getTrack())); }
				 */// throw new
					// UnsupportedOperationException("ID3v10 cannot store track numbers");
				// Year
				if (mp3file.getID3v1Tag().getYear() != null && mp3file.getID3v1Tag().getYear().size() > 0) {
					info.setYear(fieldsToString(mp3file.getID3v1Tag().getYear()));
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		} catch (TagException e) {
			e.printStackTrace();
		} catch (ReadOnlyFileException e) {
			e.printStackTrace();
		} catch (InvalidAudioFrameException e) {
			e.printStackTrace();
		}

		return info;
	}

	private String fieldsToString(List<TagField> list) {
		StringBuffer sb = new StringBuffer();
		if (list != null && list.size() > 0) {
			for (TagField field : list) {
				if (sb.length() > 0) {
					sb.append(", ");
				}
				sb.append(field.toString());
			}
		}
		return sb.toString();
	}

	private void scanResources() {
		Resource resource = null;
		try {
			// для начала ищем только те, которые добавлены, без авторизации
			List<Resource> listNewResource = this.daoAction.getResourceDao().list(" STATUS_ID=" + getStatusAdded().getId() + "", "ID");
			// потом уже ищем по имеющимся для обновлений
			listNewResource.addAll(this.daoAction.getResourceDao().list(" STATUS_ID=" + getStatusActive().getId() + "", "ID"));
			for (Resource resourceValue : listNewResource) {
				resource = resourceValue;
				// change status to Working
				resource.setStatusId(getStatusWorking().getId());
				this.daoAction.getResourceDao().save(resource);
				// start parse
				System.out.println("resource: " + resource.getUrl());
				URL url;
				url = new URL(resource.getUrl());
				String protocol = url.getProtocol();
				if ("http".equals(protocol)) {
					parseUrl(url.toString(), resource);
				}
				if ("ftp".equals(protocol)) {
					parseFtp(url, resource);
				}
				// change status to Active
				resource.setStatusId(getStatusActive().getId());
				this.daoAction.getResourceDao().save(resource);
				ResourceLog resourceLog = new ResourceLog();
				resourceLog.setResourceId(resource.getId());
				resourceLog.setUserId(resource.getUserId());//TODO add admin user in future!
				resourceLog.setStatusId(this.daoAction.getStatusDao().getByCode(Status.SCANNED).getId());
				this.daoAction.getResourceLogDao().save(resourceLog);
			}
		} catch (Exception e) {
			// change status to Error
			if (resource != null) {
				try {
					Status statusErrorAccess = this.daoAction.getStatusDao().getByCode(Status.TEMP_NOT_ACCESS);
					resource.setStatusId(statusErrorAccess.getId());
					// FIXME повесить куда нибуд сообщение
					this.daoAction.getResourceDao().save(resource);
				} catch (Exception e1) {
					log.error(e1.getMessage());
					;
				}
			}
			System.out.println(e.getMessage());
		}
	}

	FTPClient ftp = new FTPClient();
	private void parseFtp(URL url, Resource resource) {
		// Connect and logon to FTP Server
		try {
			ftp.connect(url.getHost());
			ftp.login("anonymous", "anonymous");
			ftp.setControlEncoding("UTF-8");
			System.out.println("Connected to " + url.getHost() + ".");
			System.out.print(ftp.getReplyString());
			// List the files in the directory
			String folder = url.getPath();

			parseFtpDirectory(ftp, folder, resource);

			// Logout from the FTP Server and disconnect
			ftp.logout();
			ftp.disconnect();

		} catch (SocketException e) {
			log.error("problem with parse url " + url.toString() + "\n" + e.getMessage());
			// TODO Auto-generated catch block
			// e.printStackTrace();
		} catch (IOException e) {
			log.error("problem with parse url " + url.toString() + "\n" + e.getMessage());
			// TODO Auto-generated catch block
			// e.printStackTrace();
		}

	}

	private void parseFtpDirectory(FTPClient ftp, String folder, Resource resource) throws IOException {
		boolean result = ftp.changeWorkingDirectory(folder);
		if (result) {
			FTPFile[] files = ftp.listFiles();
			DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
			for (int i = 0; i < files.length; i++) {
				Date fileDate = files[i].getTimestamp().getTime();
				// Download a file from the FTP Server
				System.out.print(files[i].getName());
				if (files[i].isFile()) {
					if (checkFileName(files[i].getName())) {
						URL fileUrl = new URL("ftp", ftp.getRemoteAddress().getHostName(), folder + WEB_SEPARATOR + files[i].getName());
						addFilesToQuery(fileUrl.toString(), files[i].getName(), resource);
					}
				}
				if (files[i].isDirectory()) {
					parseFtpDirectory(ftp, folder + WEB_SEPARATOR + files[i].getName(), resource);
				}
			}
		} else {
			log.error("can not change folder to " + folder);
		}
	}
	
	private boolean checkFileName(String filename) {
		if (filename.toLowerCase().endsWith(".mp3")) {
			return true;
		}
		return false;
	}

	private void parseUrl(String url, Resource resource) {
		try {
//			System.out.println("open url " + url);
			method = new GetMethod(url);
			int status = client.executeMethod(method);
			if (status != HttpStatus.SC_OK) {
				System.err.println("Method failed: " + method.getStatusLine());
				return;
			}
			String result = method.getResponseBodyAsString();
			// парсим
			int b;
			int e;
			while ((b = result.indexOf("<li>")) > -1 && (e = result.indexOf("</li>", b)) > b) {
				String temp = result.substring(b + "<li>".length(), e);
				result = result.substring(e);
				GetMethod method2 = new GetMethod(url);
				int ab;
				int ae;
				if ((ab = temp.indexOf("href=\"")) > -1 && (ae = temp.indexOf("\"", ab + "href=\"".length())) > ab) {
					String fileName = temp.substring(ab + "href=\"".length(), ae);
					if (fileName.matches("/$")) {
						continue;
					}
					if (fileName.matches(".*/$")) {
						if (fileName.matches("^/.*")) {
							method2.setPath(fileName);
						} else {
							method2.setPath(method2.getPath() + fileName);
						}
						//check parent
						String newUrl = method2.getURI().toString();
						if (newUrl.indexOf(url)==0 && newUrl.length()>url.length()) {
							parseUrl(newUrl, resource);
						}
//						addDirectoryToQuery(method2.getURI().toString());
					} else {
						if (checkFileName(fileName)) {
							method2.setPath(method2.getPath() + fileName);
							addFilesToQuery(method2.getURI().toString(), fileName, resource);
						}
					}
				}
			}

			// System.out.println(result);
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

//	Set<String> filesAll = new HashSet<String>();

	public static String escapeParam(String text) {
		text = text.replaceAll("'", "\\\\'");
		return text;
	}

	private void addFilesToQuery(String url, String fileName, Resource resource) {
		// нельзя подниматься выше базового урла
		try {
					if (daoAction.getContentDao().findByUrl(escapeParam(url)) == null) {
						Content foundContent = new Content();
						foundContent.setUrl(url);
						foundContent.setResourceId(resource.getId());
						foundContent.setStatusId(getStatusFounded().getId());
						foundContent.setTypeId(getTypeMusic().getId());
						foundContent.setFilename(fileName);

						daoAction.getContentDao().save(foundContent);

						System.out.println("add new file " + url);
					}
		} catch (Exception e1) {
			log.error(e1.getMessage());
			;
		}
	}

	public Status getStatusAdded() {
		if (statusAdded == null) {
			try {
				statusAdded = this.daoAction.getStatusDao().getByCode(Status.ADDED);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusAdded;
	}

	public Status getStatusWorking() {
		if (statusWorking == null) {
			try {
			statusWorking = this.daoAction.getStatusDao().getByCode(Status.WORKING);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusWorking;
	}

	public Status getStatusActive() {
		if (statusActive == null) {
			try {
			statusActive = this.daoAction.getStatusDao().getByCode(Status.ACTIVE);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusActive;
	}

	public Status getStatusFounded() {
		if (statusFounded == null) {
			try {
			statusFounded = this.daoAction.getStatusDao().getByCode(Status.FOUNDED);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusFounded;
	}

	public Status getStatusToProcess() {
		if (statusToProcess == null) {
			try {
			statusToProcess = this.daoAction.getStatusDao().getByCode(Status.TO_PROCESS);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusToProcess;
	}

	public Status getStatusWaitingСonfirm() {
		if (statusWaitingСonfirm == null) {
			try {
			statusWaitingСonfirm = this.daoAction.getStatusDao().getByCode(Status.WAITING_CONFIRM);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusWaitingСonfirm;
	}

	public Type getTypeMusic() {
		if (typeMusic == null) {
			try {
			typeMusic = this.daoAction.getTypeDao().getByCode(Type.MUSIC);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return typeMusic;
	}

	public Status getStatusNotAccessForProcess() {
		if (statusNotAccessForProcess == null) {
			try {
			statusNotAccessForProcess = this.daoAction.getStatusDao().getByCode(Status.NOT_ACCESS_FOR_PROCESS);
			} catch (SQLException e) {
				log.error(e.getMessage());
			}
		}
		return statusNotAccessForProcess;
	}

}
